package scala.scalanative
package unsigned

import scalanative.runtime.Intrinsics.{castIntToRawSizeUnsigned, unsignedOf}

/** `UByte`, a 8-bit unsigned integer. */
final class UByte private[scalanative] (
    private[scalanative] val underlyingValue: Byte
) extends scala.math.ScalaNumber
    with java.io.Serializable
    with Comparable[UByte] {

  @inline final def toByte: Byte = underlyingValue
  @inline final def toShort: Short = toInt.toShort
  @inline final def toChar: Char = toInt.toChar
  @inline final def toInt: Int = underlyingValue & 0xff
  @inline final def toLong: Long = toInt.toLong
  @inline final def toFloat: Float = toInt.toFloat
  @inline final def toDouble: Double = toInt.toDouble

  @inline final def toUByte: UByte = this
  @inline final def toUShort: UShort = unsignedOf(toShort)
  @inline final def toUInt: UInt = unsignedOf(toInt)
  @inline final def toULong: ULong = unsignedOf(toLong)
  @inline final def toUSize: USize = unsignedOf(castIntToRawSizeUnsigned(toInt))

  @inline override def doubleValue(): Double = toDouble
  @inline override def floatValue(): Float = toFloat
  @inline override def intValue(): Int = toInt
  @inline override def longValue(): Long = toLong
  @inline override protected def isWhole(): Boolean = true
  @inline override def underlying(): Object = this

  /** Returns the bitwise negation of this value.
   *  @example
   *    {{{~5 == -6 // in binary: ~00000101 == // 11111010}}}
   */
  @inline final def unary_~ : UInt = ~toUInt

  /** Returns this value bit-shifted left by the specified number of bits,
   *  filling in the new right bits with zeroes.
   *  @example
   *    {{{6 << 3 == 48 // in binary: 0110 << 3 == 0110000}}}
   */
  @inline final def <<(x: Int): UInt = toUInt << x

  /** Returns this value bit-shifted left by the specified number of bits,
   *  filling in the new right bits with zeroes.
   *  @example
   *    {{{6 << 3 == 48 // in binary: 0110 << 3 == 0110000}}}
   */
  @inline final def <<(x: Long): UInt = toUInt << x

  /** Returns this value bit-shifted right by the specified number of bits,
   *  filling the new left bits with zeroes.
   *  @example
   *    {{{21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010}}}
   *  @example
   *    {{{ 4294967275 >>> 3 == 536870909 // in binary: 11111111 11111111
   *    11111111 11101011 >>> 3 == // 00011111 11111111 11111111 11111101 }}}
   */
  @inline final def >>>(x: Int): UInt = toUInt >>> x

  /** Returns this value bit-shifted right by the specified number of bits,
   *  filling the new left bits with zeroes.
   *  @example
   *    {{{21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010}}}
   *  @example
   *    {{{ 4294967275 >>> 3 == 536870909 // in binary: 11111111 11111111
   *    11111111 11101011 >>> 3 == // 00011111 11111111 11111111 11111101 }}}
   */
  @inline final def >>>(x: Long): UInt = toUInt >>> x

  /** Returns this value bit-shifted left by the specified number of bits,
   *  filling in the right bits with the same value as the left-most bit of
   *  this.
   *  @example
   *    {{{ 4294967275 >> 3 == 4294967293 // in binary: 11111111 11111111
   *    11111111 11101011 >> 3 == // 11111111 11111111 11111111 11111101 }}}
   */
  @inline final def >>(x: Int): UInt = toUInt >> x

  /** Returns this value bit-shifted left by the specified number of bits,
   *  filling in the right bits with the same value as the left-most bit of
   *  this.
   *  @example
   *    {{{ 4294967275 >> 3 == 4294967293 // in binary: 11111111 11111111
   *    11111111 11101011 >> 3 == // 11111111 11111111 11111111 11111101 }}}
   */
  @inline final def >>(x: Long): UInt = toUInt >> x

  @inline override final def compareTo(x: UByte): Int =
    (underlyingValue & 0xff) - (x.underlyingValue & 0xff)

  /** Returns `true` if this value is equal to x, `false` otherwise. */
  @inline final def ==(x: UByte): Boolean = underlyingValue == x.underlyingValue

  /** Returns `true` if this value is equal to x, `false` otherwise. */
  @inline final def ==(x: UShort): Boolean = toUInt == x.toUInt

  /** Returns `true` if this value is equal to x, `false` otherwise. */
  @inline final def ==(x: UInt): Boolean = toUInt == x

  /** Returns `true` if this value is equal to x, `false` otherwise. */
  @inline final def ==(x: ULong): Boolean = toULong == x

  /** Returns `true` if this value is not equal to x, `false` otherwise. */
  @inline final def !=(x: UByte): Boolean = underlyingValue != x.underlyingValue

  /** Returns `true` if this value is not equal to x, `false` otherwise. */
  @inline final def !=(x: UShort): Boolean = toUInt != x.toUInt

  /** Returns `true` if this value is not equal to x, `false` otherwise. */
  @inline final def !=(x: UInt): Boolean = toUInt != x

  /** Returns `true` if this value is not equal to x, `false` otherwise. */
  @inline final def !=(x: ULong): Boolean = toULong != x

  /** Returns `true` if this value is less than x, `false` otherwise. */
  @inline final def <(x: UByte): Boolean = toUInt < x.toUInt

  /** Returns `true` if this value is less than x, `false` otherwise. */
  @inline final def <(x: UShort): Boolean = toUInt < x.toUInt

  /** Returns `true` if this value is less than x, `false` otherwise. */
  @inline final def <(x: UInt): Boolean = toUInt < x

  /** Returns `true` if this value is less than x, `false` otherwise. */
  @inline final def <(x: ULong): Boolean = toULong < x

  /** Returns `true` if this value is less than or equal to x, `false`
   *  otherwise.
   */
  @inline final def <=(x: UByte): Boolean = toUInt <= x.toUInt

  /** Returns `true` if this value is less than or equal to x, `false`
   *  otherwise.
   */
  @inline final def <=(x: UShort): Boolean = toUInt <= x.toUInt

  /** Returns `true` if this value is less than or equal to x, `false`
   *  otherwise.
   */
  @inline final def <=(x: UInt): Boolean = toUInt <= x

  /** Returns `true` if this value is less than or equal to x, `false`
   *  otherwise.
   */
  @inline final def <=(x: ULong): Boolean = toULong <= x

  /** Returns `true` if this value is greater than x, `false` otherwise. */
  @inline final def >(x: UByte): Boolean = toUInt > x.toUInt

  /** Returns `true` if this value is greater than x, `false` otherwise. */
  @inline final def >(x: UShort): Boolean = toUInt > x.toUInt

  /** Returns `true` if this value is greater than x, `false` otherwise. */
  @inline final def >(x: UInt): Boolean = toUInt > x

  /** Returns `true` if this value is greater than x, `false` otherwise. */
  @inline final def >(x: ULong): Boolean = toULong > x

  /** Returns `true` if this value is greater than or equal to x, `false`
   *  otherwise.
   */
  @inline final def >=(x: UByte): Boolean = toUInt >= x.toUInt

  /** Returns `true` if this value is greater than or equal to x, `false`
   *  otherwise.
   */
  @inline final def >=(x: UShort): Boolean = toUInt >= x.toUInt

  /** Returns `true` if this value is greater than or equal to x, `false`
   *  otherwise.
   */
  @inline final def >=(x: UInt): Boolean = toUInt >= x

  /** Returns `true` if this value is greater than or equal to x, `false`
   *  otherwise.
   */
  @inline final def >=(x: ULong): Boolean = toULong >= x

  /** Returns the bitwise OR of this value and `x`. */
  @inline final def |(x: UByte): UInt = this.toUInt | x.toUInt

  /** Returns the bitwise OR of this value and `x`. */
  @inline final def |(x: UShort): UInt = this.toUInt | x.toUInt

  /** Returns the bitwise OR of this value and `x`. */
  @inline final def |(x: UInt): UInt = this.toUInt | x

  /** Returns the bitwise OR of this value and `x`. */
  @inline final def |(x: ULong): ULong = this.toULong | x

  /** Returns the bitwise AND of this value and `x`. */
  @inline final def &(x: UByte): UInt = this.toUInt & x.toUInt

  /** Returns the bitwise AND of this value and `x`. */
  @inline final def &(x: UShort): UInt = this.toUInt & x.toUInt

  /** Returns the bitwise AND of this value and `x`. */
  @inline final def &(x: UInt): UInt = this.toUInt & x

  /** Returns the bitwise AND of this value and `x`. */
  @inline final def &(x: ULong): ULong = this.toULong & x

  /** Returns the bitwise XOR of this value and `x`. */
  @inline final def ^(x: UByte): UInt = this.toUInt ^ x.toUInt

  /** Returns the bitwise XOR of this value and `x`. */
  @inline final def ^(x: UShort): UInt = this.toUInt ^ x.toUInt

  /** Returns the bitwise XOR of this value and `x`. */
  @inline final def ^(x: UInt): UInt = this.toUInt ^ x

  /** Returns the bitwise XOR of this value and `x`. */
  @inline final def ^(x: ULong): ULong = this.toULong ^ x

  /** Returns the sum of this value and `x`. */
  @inline final def +(x: UByte): UInt = this.toUInt + x.toUInt

  /** Returns the sum of this value and `x`. */
  @inline final def +(x: UShort): UInt = this.toUInt + x.toUInt

  /** Returns the sum of this value and `x`. */
  @inline final def +(x: UInt): UInt = this.toUInt + x

  /** Returns the sum of this value and `x`. */
  @inline final def +(x: ULong): ULong = this.toULong + x

  /** Returns the difference of this value and `x`. */
  @inline final def -(x: UByte): UInt = this.toUInt - x.toUInt

  /** Returns the difference of this value and `x`. */
  @inline final def -(x: UShort): UInt = this.toUInt - x.toUInt

  /** Returns the difference of this value and `x`. */
  @inline final def -(x: UInt): UInt = this.toUInt - x

  /** Returns the difference of this value and `x`. */
  @inline final def -(x: ULong): ULong = this.toULong - x

  /** Returns the product of this value and `x`. */
  @inline final def *(x: UByte): UInt = this.toUInt * x.toUInt

  /** Returns the product of this value and `x`. */
  @inline final def *(x: UShort): UInt = this.toUInt * x.toUInt

  /** Returns the product of this value and `x`. */
  @inline final def *(x: UInt): UInt = this.toUInt * x

  /** Returns the product of this value and `x`. */
  @inline final def *(x: ULong): ULong = this.toULong * x

  /** Returns the quotient of this value and `x`. */
  @inline final def /(x: UByte): UInt = this.toUInt / x.toUInt

  /** Returns the quotient of this value and `x`. */
  @inline final def /(x: UShort): UInt = this.toUInt / x.toUInt

  /** Returns the quotient of this value and `x`. */
  @inline final def /(x: UInt): UInt = this.toUInt / x

  /** Returns the quotient of this value and `x`. */
  @inline final def /(x: ULong): ULong = this.toULong / x

  /** Returns the remainder of the division of this value by `x`. */
  @inline final def %(x: UByte): UInt = this.toUInt % x.toUInt

  /** Returns the remainder of the division of this value by `x`. */
  @inline final def %(x: UShort): UInt = this.toUInt % x.toUInt

  /** Returns the remainder of the division of this value by `x`. */
  @inline final def %(x: UInt): UInt = this.toUInt % x

  /** Returns the remainder of the division of this value by `x`. */
  @inline final def %(x: ULong): ULong = this.toULong % x

  @inline override final def toString(): String = toInt.toString()

  @inline override def hashCode(): Int = underlyingValue.##

  @inline override def equals(obj: Any): Boolean = obj match {
    case that: UByte => this.underlyingValue == that.underlyingValue
    case _           => false
  }

  // "Rich" API

  @inline final def max(that: UByte): UByte =
    this.toUInt.max(that.toUInt).toUByte
  @inline final def min(that: UByte): UByte =
    this.toUInt.min(that.toUInt).toUByte

  @inline final def toBinaryString: String = toUInt.toBinaryString
  @inline final def toHexString: String = toUInt.toHexString
  @inline final def toOctalString: String = toUInt.toOctalString
}

object UByte {

  /** The smallest value representable as a UByte. */
  final val MinValue = unsignedOf(0.toByte)

  /** The largest value representable as a UByte. */
  final val MaxValue = unsignedOf((-1).toByte)

  /** The String representation of the scala.UByte companion object. */
  override def toString(): String = "object scala.UByte"

  /** Language mandated coercions from UByte to "wider" types. */
  import scala.language.implicitConversions
  implicit def ubyte2ushort(x: UByte): UShort = x.toUShort
  implicit def ubyte2uint(x: UByte): UInt = x.toUInt
  implicit def ubyte2ulong(x: UByte): ULong = x.toULong

  @inline def valueOf(byteValue: scala.Byte): UByte = {
    import UByteCache.cache
    val idx = byteValue - scala.Byte.MinValue
    val cached = cache(idx)
    if (cached ne null) cached
    else {
      val newBox = new UByte(byteValue)
      cache(idx) = newBox
      newBox
    }
  }
}

private[unsigned] object UByteCache {
  private[unsigned] val cache = new Array[UByte](256)
}
